<?php
require_once('entities.inc.php');

class Blog {
	public static function grok_time($text) {
		if (preg_match('|^\s*([0-9]{4})[-/.]([0-9]{1,2})[-/.] ?([0-9]{1,2})\s*$|', $text, $m)) {
			return mktime(12,0,0,intval($m[2],10),intval($m[3],10),intval($m[1],10));
		} else if (preg_match('#^\s*([0-9]{4})[-/.]([0-9]{1,2})[-/.] ?([0-9]{1,2})\s+(0?[0-9]|1[0-9]|2[0-3])[h:]?([0-5]?[0-9])\s*$#', $text, $m)) {
			return mktime(intval($m[4],10),intval($m[5],10),0,intval($m[2],10),intval($m[3],10),intval($m[1],10));
		} else {
			return strtotime($text);
		}
	}

	public static function make_anchor_friendly($text) {
		return urlencode(str_replace(' ', '_', strip_tags($text)));
	}

	public static function make_title_friendly($text) {
		return Entities::revert_entities(strip_tags($text));
	}

	public static function reorder(&$array, $order) {
		if ($order == 'stars' || $order == '-stars') {
			$blogs = array();
			foreach ($array as $bk=>$bb) {
				$key = $bb['stars'];
				if (!isset($blogs[$key])) $blogs[$key] = array();
				$blogs[$key][$bk] = $bb;
			}
			$array = array();
			if ($order{0} == '-') {
				ksort($blogs);
			} else {
				krsort($blogs);
			}
			foreach ($blogs as $bl) {
				krsort($bl);
				foreach ($bl as $bk=>$bb) {
					$array[$bk] = $bb;
				}
			}
		} else if ($order == '-date') {
			ksort($array);
		} else /* $order == 'date' */ {
			krsort($array);
		}
	}

	public static function get_headers(&$text) {
		$headers = array();
		while ($text && preg_match('/^\s*([A-Z0-9.-]+)\s*:\s*([^\n]*)\n/i', $text, $m)) {
			$headers[strtolower($m[1])] = $m[2];
			$text = substr($text, strlen($m[0]));
		}
		if (substr($text,0,1) == "\n") {
			$text = substr($text, 1);
		} else {
			$headers['$WARNING$'] = "headers not followed by blank line;  something may be broken";
		}
		return $headers;
	}

	private $limit = 10;
	private $first = 0;
	private $tag   = '';
	private $order = '';
	private $article = 0;
	private $do_rss  = false;

	private $next  = false;
	private $prev  = false;

	private $all_tags = array();
	private $blogs    = array();
	private $errors   = array();
	private $most_recent = 0;

	private $settings = array();

	public function Blog($settings) {
		$this->settings = $settings;

		/*
		 * Parse Parameters (from $_REQUEST)
		 */
		if (isset($_REQUEST['a'])) {
			$x = $_REQUEST['a'];
			if (preg_match('/^([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})$/', $x, $m)) {
				$this->article = mktime($m[4], $m[5], 0, $m[2], $m[3], $m[1]);
			}
		}

		if (isset($_REQUEST['n'])) {
			$x = intval($_REQUEST['n']);
			if ($x) {
				$this->limit = $x;
			} else if ($_REQUEST['n'] == 'all') {
				$this->limit = -1;
			}
		}

		if (isset($_REQUEST['s'])) {
			$x = intval($_REQUEST['s']);
			if ($x) {
				$this->first = $x;
			}
		}

		if (isset($_REQUEST['tag'])) {
			$this->tag = strtolower(trim($_REQUEST['tag']));
		}

		if (isset($_REQUEST['order'])) {
			$this->order = strtolower(trim($_REQUEST['order']));
		}

		if (isset($_GET['rss']) && $_GET['rss'] != 'no') {
			$this->do_rss = true;
		}
	}

	/*
	 * Load blog entries (from filesystem).
	 */
	public function load() {
		$dirname = $this->settings['ENTRY_PATH'];

		$this->blogs = array();
		$this->all_tags = array();
		$this->most_recent = 0;

		$d = dir($dirname);
		if ($d) {
			while (false !== ($entry = $d->read())) {
				$file = "${dirname}/${entry}";
				if ($entry && $entry{0} != '.' && substr($entry, -5) == '.blog' && !is_dir($file)) {
					$contents = file_get_contents($file);
					$headers = Blog::get_headers($contents);
					if (isset($headers['heading']) && !isset($headers['title'])) {
						$headers['title'] = $headers['heading'];
					}
					if (isset($headers['date']) && isset($headers['title'])) {
						// get item BODY
						$body = trim($contents);
						if (!preg_match('/<(p|div|table)\b/', $body)) {
							$body = '<p>' . $body . '</p>';
						}

						// get item TAGS
						$tags_arr = array();
						$tags = '';
						if (isset($headers['tags']) && ($headers['tags'] = preg_replace('/\s+/', '', $headers['tags']))) {
							$xptags = explode(",", $headers['tags']);
							$this->all_tags = array_merge($this->all_tags, array_flip($xptags));

							$tags = array();
							foreach ($xptags as $ttag) {
								$ttag = trim($ttag);
								$tags[] = sprintf('<a href="%s?tag=%s%s%s">%s</a>', $this->settings['ADDRESS'], strtolower($ttag), ($this->limit == 10 ? '' : "&amp;n=" . $this->limit), ($this->first == 0 ? '' : "&amp;s=" . $this->first), $ttag);
								$tags_arr[] = strtolower($ttag);
							}
							$tags = '<p class="tags">Tags: ' . implode(', ', $tags) . "</p>\n";
						}

						// get item STARS
						$stars = 0;
						if (isset($headers['stars']) && $headers['stars']) {
							if (preg_match('/^\*+$/', $headers['stars'])) {
								$stars = strlen($headers['stars']);
							} else {
								$stars = intval($headers['stars']);
							}
						}

						// record item
						$time = Blog::grok_time($headers['date']);
						if (!$this->article || ($time == $this->article)) {
							if ((!$this->tag) || in_array($this->tag, $tags_arr)) {
								if ($time) {
									if ($time > $this->most_recent) $this->most_recent = $time;
									$pubDate = date('r', $time);
									$link = '?a=' . date('YmdHi', $time);
									$date = date('Y-m-d H:i', $time);
								} else {
									// deprecated. todo: remove this functionality?
									$time = $date = $m[1];
									$pubDate = $m[1];
									$link = '#' . Blog::make_anchor_friendly($headers['title']);
								}
								$this->blogs[$time] = array(
									'title'=>Blog::make_title_friendly($headers['title']),
									'anchor'=>Blog::make_anchor_friendly($headers['title']),
									'link'=>$this->settings['ADDRESS'] . $link,
									'tags'=>$tags,
									'stars'=>$stars,
									'body'=>$body,
									'pubDate'=>$pubDate,
									'date'=>$date,
									'guid'=>md5($entry),
								);
							}
						}
					} else {
						$this->errors[] = substr($entry, 0, -5);
					}
				}
			}
			Blog::reorder($this->blogs, $this->order);

			$this->prev = false;
			$this->next = false;
			$total = count($this->blogs);
			if ($this->limit > 0 && $total > $this->limit) {
				$this->next = ($this->first + $this->limit < $total);
				$this->prev = ($this->first > 0);
				$this->blogs = array_slice($this->blogs, $this->first, $this->limit);
			}

			$this->all_tags = array_keys($this->all_tags);
		}
	}

	public function display($header = false) {
		if ($this->do_rss) {
			if ($header) header('Content-type: text/xml; charset=iso-8859-1');
			echo $this->rss();
		} else {
			if ($header) header('Content-type: text/html; charset=iso-8859-1');
			echo $this->html();
		}
	}

	public function rss() {
		$template = file_get_contents($this->settings['LIBRARY_PATH'] . 'rss.template');

		$old = array('%NOW%',   '%MOST_RECENT%');
		$new = array(date('r'), date('r', $this->most_recent));

		foreach ($this->settings as $k=>$v) {
			$old[] = "%${k}%";
			$new[] = $v;
		}

		$old[] = '%ITEMS%';
		$new[] = $this->rss_items();

		return str_replace($old, $new, $template);
	}

	private function rss_items() {
		$template = file_get_contents($this->settings['LIBRARY_PATH'] . 'rss-entry.template');
		$result = '';
		foreach ($this->blogs as $blog) {
			$old = array();
			$new = array();
			foreach ($blog as $k=>$v) {
				$old[] = "%${k}%";
				$new[] = $v;
			}
			$result .= str_replace($old, $new, $template);
		}
		return $result;
	}

	public function html() {
		$template = file_get_contents($this->settings['LIBRARY_PATH'] . 'html.template');

		$old = array('%NOW%',   '%MOST_RECENT%');
		$new = array(date('r'), date('r', $this->most_recent));

		$old[] = '%ADDRESS_BASE%';
		$new[] = dirname($_SERVER['PHP_SELF']);

		$old[] = '%LAST_10%';
		$new[] = $this->link_for_last(10);

		$old[] = '%LAST_20%';
		$new[] = $this->link_for_last(20);

		$old[] = '%LAST_ALL%';
		$new[] = $this->link_for_last(-1);

		$old[] = '%ORDER_FORM%';
		$new[] = Blog::order_form();

		$old[] = '%NEXT_PREV_INLINE%';
		$new[] = Blog::next_prev(true);

		$old[] = '%NEXT_PREV%';
		$new[] = Blog::next_prev();

		$old[] = '%RSS_LINK%';
		$new[] = $this->settings['ADDRESS'] . '?rss' . ($this->limit != 10 ? '&amp;n=' . $this->limit : '');

		$old[] = '%CURRENT_TAGS%';
		if ($this->tag) {
			$new[] = "\n<p class=\"tags\">[&nbsp;Viewing Tag: " . $this->tag . "&nbsp;| <a href=\"" . $this->settings['ADDRESS'] . "?" . ($this->limit == 10 ? '' : "n=$this->limit&amp;") . "s=" . max($this->first - $this->limit, 0) . "\">all</a>&nbsp;]</p>";
		} else {
			$new[] = '';
		}

		foreach ($this->settings as $k=>$v) {
			$old[] = "%${k}%";
			$new[] = $v;
		}

		$alltags_string = '';
		$alltags = $this->all_tags;
		asort($alltags);
		foreach ($alltags as $ttag) {
			$alltags_string .= sprintf(' <a href="%s?tag=%s%s">%s</a>', $this->settings['ADDRESS'], strtolower($ttag), ($this->limit == 10 ? '' : "&amp;n=$this->limit") . ($this->first > 0 ? "&amp;s=$this->first" : '') . ($this->order > 0 ? "&amp;order=$this->order" : ''), $ttag);
		}
		if (!$alltags_string) $alltags_string = '&mdash;';
		$old[] = '%ALL_TAGS%';
		$new[] = $alltags_string;

		$old[] = '%ITEMS%';
		$new[] = $this->html_items();

		$old[] = '%FontSerif%';
		$new[] = '"DejaVu Serif",     "Lucida Bright",  serif';

		$old[] = '%FontSans%';
		$new[] = '"DejaVu Sans",      "Lucida Sans",    sans-serif';

		$old[] = '%FontMono%';
		$new[] = '"DejaVu Sans Mono", "Lucida Console", monospace';

		return str_replace($old, $new, $template);
	}

	private function html_items() {
		$result = '';
		if ($this->errors) {
			$result .= '<i>The following entries could not be parsed!<br><ul>';
			foreach ($this->errors as $err) {
				$result .= '<li>' . $err . '</li>';
			}
			$result .= '</ul></i><hr>';
		}
		if ($this->blogs) {
			$template = file_get_contents($this->settings['LIBRARY_PATH'] . 'html-entry.template');
			$i = $this->first;
//echo '<pre>...' . str_replace('<','&lt;',print_r($blog,1)) . '...</pre>';
//echo '<pre>...' . str_replace('<','&lt;',print_r($this->blogs,1)) . '...</pre>';
			foreach ($this->blogs as $blog) {
				$old = array('%i%');
				$new = array(++ $i);
//echo '<pre>...' . str_replace('<','&lt;',print_r($blog,1)) . '...</pre>';
				foreach ($blog as $k=>$v) {
					$old[] = "%${k}%";
					if ($k == 'stars') {
						if ($v) {
							$starstr = ' ';
							while ($v) {
								$starstr .= '<img src="' . $this->settings['ADDRESS'] . $this->settings['LIBRARY_PATH'] . 'star.png" width="21" height="20">';
								-- $v;
							}
							$new[] = $starstr;
						} else {
							$new[] = '';
						}
					} else {
						$new[] = $v;
					}
				}
				$result .= str_replace($old, $new, $template);
			}
		} else {
			$result .= '<i>There are no entries.</i>';
		}
		return $result;
	}

	public function link_for_last($n) {
		if ($n <= 0) {
			if ($this->limit > 0 || $this->article) {
				return '<a href="' . $this->settings['ADDRESS'] . '?n=-1' . ($this->tag ? "&amp;tag=$this->tag" : '') . ($this->order ? "&amp;order=$this->order" : '') . '">all</a>';
			} else {
				return 'all';
			}
		} else if ($this->limit != $n || $this->article) {
			if ($n == 10) {
				return '<a href="' . $this->settings['ADDRESS'] . '?'
					. ($this->first ? "s=$this->first" : '')
					. ($this->tag ? ($this->first ? '&amp;' : '') . "tag=$this->tag" : '')
					. ($this->order ? ($this->first . $this->tag ? '&amp;' : '') . "order=$this->order" : '')
					. '">10</a>';
			} else {
				return '<a href="' . $this->settings['ADDRESS'] . '?n=' . $n . ($this->first ? "&amp;s=$this->first" : '') . ($this->tag ? "&amp;tag=$this->tag" : '') . ($this->order ? "&amp;order=$this->order" : '') . '">' . $n . '</a>';
			}
		} else {
			return $n;
		}
	}

	function next_prev($no_p = false) {
		$result = '';
		if ($this->prev || $this->next) {
			if (!$no_p) $result .= '<p style="float:right" class="noprint">';
			else $result .= '<br>';
			if ($this->prev) {
				$result .= "<a href=\"" . $this->settings['ADDRESS'] . "?" . (($this->limit == 10 && !$this->article) ? '' : "n=$this->limit&amp;") . "s=" . max($this->first - $this->limit, 0) . ($this->tag ? "&amp;tag=$this->tag" : '') . ($this->order ? "&amp;order=$this->order" : '') . "\" title=\"Previous\">&laquo;</a>";
			} else {
				$result .= '&lsaquo;';
			}
			$result .= ' : ';
			if ($this->next) {
				$result .= "<a href=\"" . $this->settings['ADDRESS'] . "?" . (($this->limit == 10 && !$this->article) ? '' : "n=$this->limit&amp;") . "s=" . ($this->first + $this->limit) . ($this->tag ? "&amp;tag=$this->tag" : '') . ($this->order ? "&amp;order=$this->order" : '') . "\" title=\"Next\">&raquo;</a> ";
			} else {
				$result .= '&rsaquo;';
			}
			if (!$no_p) $result .= "</p>\n";
		}
		return $result;
	}

	function order_form() {
		$result = '';
		$rid = md5(mt_rand());
		$result .= '<div style="float:right; text-align:left; clear:right; font-family:sans-serif;font-size:70%;">Order:';
		$result .= '<form method="GET" action="' . $this->settings['ADDRESS'] . '" id="' . $rid . '">';
		if ($this->limit != 10) $result .= '<input type="hidden" name="n" value="' . $this->limit . '">';
		if ($this->first) $result .= '<input type="hidden" name="s" value="' . $this->first . '">';
		if ($this->tag)   $result .= '<input type="hidden" name="tag" value="' . $this->tag . '">';
		$result .= '<select name="order" onchange="javascript:document.getElementById(\'' . $rid . '\').submit();">';
		foreach (array('date'=>'date', '-date'=>'date (rev)', 'stars'=>'stars', '-stars'=>'stars (rev)') as $v=>$o) {
			$result .= '<option value="' . $v . '"' . ($this->order == $v ? ' selected="selected"' : '') . '>' . $o . '</option>';
		}
		$result .= '</select>';
		$result .= '</form>';
		$result .= '</div>';
		return $result;
	}
}

